异步处理

阶段一:回调函数

当异步任务完成,调用对应回调函数(一般有两个:成功回调和失败回调),并将数据或错误以函数参数传入

阶段二:Promise

ES6 新增的 API,目的是为了解决异步处理中回调函数方式的一些问题:

  1. 回调地狱:回调函数层层嵌套(回调套回调),代码难以阅读和维护
  2. 控制反转:如不能保证回调函数被正确调用(一个回调可能被调用多次、成功回调和失败回调都被调用)

Promise 如何解决?

  1. .then(...).then(...) 打平,无需嵌套
  2. Promise 的状态只能够改变一次(pending → resolvedpending → rejected

Promise 可以完全代替回调函数?

不能!

阶段三:Generator 函数

Generator 函数可以退出,并在稍后重新进入,其上下文(变量绑定)会在重新进入时保存

function* 声明创建一个 Generator 函数对象。每次调用 Generator 函数时,它都会返回一个新的 Generator 对象,该对象符合[[011.迭代协议#迭代器协议|迭代器协议]]。当迭代器的 next() 方法被调用时,生成器函数的主体会被执行,直到遇到第一个 yield 表达式,该表达式指定了迭代器要返回的值,或者用 yield* 委托给另一个生成器函数。next() 方法返回一个对象,其 value 属性包含了 yield 表达式的值,done 属性是布尔类型,表示生成器是否已经返回了最后一个值。如果 next() 方法带有参数,那么它会恢复生成器函数的执行,并用参数替换暂停执行的 yield 表达式​

其本质就是一个状态机,详见:[[010.Generator 函数|Generator 函数]]

阶段四:asyncawait

Promise + GeneratorFunction 的语法糖,可以写看起来像同步的代码而完成异步编程

示例:

function* fetchUser() {
  const data = yield fetch('/api/user');
  console.log(data);
}
// 手动执行(实际中常用 co 库或 async/await)
const g = fetchUser();
g.next().value.then(data => g.next(data));

Promise

function loadScript(src) {
    // 初始状态:pending,数据:undefined
    // 状态改变后无法逆转
    return new Promise((resolve, reject) => {
        const script = document.createElement('script')
        script.src = src
        script.onload = () = resolve(src)  // 状态:fulfilled,数据:result
        script.onerror = err => reject(err) // 状态:rejected,数据:error
        document.head.append(script)
    })
}

loadScript('./1.js')
    .then(() => loadScript('./2.js'))
    .then(() => loadScript('./3.js'))
    .catch(err => console.log(err))

APIs

// Promise.all
const p1 = Promise.resolve(1)
const p2 = 2
const p3 = Promise.resolve(3)
Promise.all([p1, p2, p3]).then(result => {
    console.log(result) // [1, 2, 3]
})

// Promise.race
const p1 = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(1), 1000)
    })
}
const p2 = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(2), 0)
    })
}

Promise.race([p1(), p2()]).then(result => {
    console.log(result) // 2
)

知识点

状态吸收

Promise 状态吸收是指:

以下三种情况下,涉及 Promise 的状态吸收:

// 情况 1: .then 的参数回调函数返回了一个 Promise(.catch 一样)
const p2 = p.then(() => p1)
// p2 要吸收 p1 的状态

// 情况 2: async 函数返回了一个 Promise
const p4 = await asyncFunc()
async function asyncFunc() {
  return p3
}
// p4 要吸收 p3 的状态

// 情况 3: resolve 接收了一个 Promise(reject 一样)
const p6 = new Promise((resolve, reject) => {
  resolve(p5)
})
// p6 要吸收 p5 的状态

// 注意:Promise.resolve() 接收 Promise 参数不会状态吸收
const p8 = Promise.resolve(p7)
// 这里的 p8 === p7

// 注意:finally() 返回的新 Promise p10 与 p9 状态一致,但没有状态吸收
const p10 = p9.finally()

Promise A+ 规范没有详细规定如何状态吸收,如何保持状态一致取决于 JS 引擎具体实现,下面说一下 Chrome 和 Node.js 的 V8 的实现方式

V8 将状态吸收分为两个步骤:

  1. 准备
  2. 吸收

每一个步骤都是放到微队列中运行

拿上面的情况 1 举例:

题目

1

async function async1() {
	console.log(1);
	await async2();
	console.log(2);
}

async function async2() {
	console.log(3);
}

console.log(4);

setTimeout(function () {
	console.log(5);
}, 0)

async1();

new Promise(function (resolve) {
	console.log(6);
	resolve();
}).then(function () {
	console.log(7);
});

console.log(8);

2

console.log(1);

setTimeout(() => {
	console.log(2);
	Promise.resolve().then(() => {
		console.log(3);
	})
}, 0);

new Promise(function (resolve, reject) {
	console.log(4);
	setTimeout(function () {
		console.log(5);
		resolve(6);
	}, 0);
}).then((res) => {
	console.log(7);
	setTimeout(() => {
		console.log(res);
	}, 0);
})

3

const p = function() {
	return new Promise((resolve, reject) => {
		const p1 = new Promise((resolve, reject) => {
			setTimeout(() => {
				resolve(1);
			}, 0);
			resolve(2);
		});
	
		p1.then((res) => {
			console.log(res);
		})
	
		console.log(3);
		resolve(4);
	});
}

p().then((res) => {
	console.log(res);
});
console.log ('end');

4

const p = function() {
	return new Promise((resolve, reject) => {
		const p1 = new Promise((resolve, reject) => {
			setTimeout(() => {
				resolve(1);
			}, 0);
//			resolve(2);
		});
	
		p1.then((res) => {
			console.log(res);
		})
	
		console.log(3);
		resolve(4);
	});
}

p().then((res) => {
	console.log(res);
});
console. log ('end');

5

async function f1() {
  console.log(1)
  await f2()
  console.log(2)
}

f2 = async () => {
  await setTimeout(() => {
    Promise.resolve().then(() => {
	  console.log(3)
    })
    console.log(4)
  })
}

f3 = async () => {
  Promise.resolve().then(() => {
    console.log(6)
  })
}

f1()
console.log(7)
f3()

6

async function f1() {
  console.log(1)
  await f2()
  console.log(2)
}

f2 = async () => {
  await (async () => {
    await (() => {
      console.log(3)
    })()
    console.log(4)
  })()
}

f3 = async () => {
  Promise.resolve().then(() => {
    console.log(6)
  })
}

f1()
console.log(7)
f3()

7

const p1 = new Promise((resolve, reject) => {
  resolve()
})

const p2 = new Promise((resolve, reject) => {
  resolve(p1)
})

p2.then(() => {
  console.log('1')
})
  .then(() => {
    console.log('2')
  })
  .then(() => {
    console.log('3')
  })

p1.then(() => {
  console.log('4')
})
  .then(() => {
    console.log('5')
  })
  .then(() => {
    console.log('6')
  })

8

async function async1() {
  console.log(1)
  await async2()
  console.log('AAA')
}

async function async2() {
  return Promise.resolve(2)
}

async1()

Promise.resolve()
  .then(() => console.log(3))
  .then(() => console.log(4))
  .then(() => console.log(5))

9

async function async1() {
  console.log(1)
  await async2()
  console.log('AAA')
}

function async2() {
  return Promise.resolve(2)
}

async1()

Promise.resolve()
  .then(() => console.log(3))
  .then(() => console.log(4))
  .then(() => console.log(5))

10

Promise.resolve().then(() => {
  console.log(0)
  return Promise.resolve(4)
}).then(res => {
  console.log(res)
})

Promise.resolve().then(() => {
  console.log(1)
}).then(res => {
  console.log(2)
}).then(res => {
  console.log(3)
}).then(res => {
  console.log(5)
}).then(res => {
  console.log(6)
})